using System;
using System.Configuration;
using System.Reflection;
using Needle.Core.Injection;
using Needle.Utils;

namespace Needle.Core.Configuration {
    /// <summary>
    /// Describes an injected property
    /// </summary>
    public class PropertyElement : ConfigurationElement {
   
        /// <summary>
        /// Creates a new instance
        /// </summary>
        public PropertyElement() {
        }

        /// <summary>
        /// Creates a new instance
        /// </summary>
        public PropertyElement(string name, InstanceId dependency) {
            Ensure.NotNull(name, Element.Name);
            Name = name;
            if (dependency != null)
                Inject = dependency.ToString();
        }

        /// <summary>
        /// The name of the property
        /// </summary>
        [ConfigurationProperty(Element.Name, IsRequired = true)]
        public string Name {
            get { return (string) this[Element.Name]; }
            set { this[Element.Name] = value; }
        }
        /// <summary>
        /// The dependency to inject
        /// </summary>
        [ConfigurationProperty(Element.Inject, IsRequired = false)]
        public string Inject {
            get { return (string)this[Element.Inject]; }
            set { this[Element.Inject] = value; }
        }

        /// <summary>
        /// The order of injection
        /// </summary>
        [ConfigurationProperty(Element.Order, IsRequired = false)]
        public int Order
        {
            get { return (int)this[Element.Order]; }
            set { this[Element.Order] = value; }
        }

        internal Needle.Core.Injection.IInjectionSite GetInjectionSite(Type injectedType)
        {
            PropertyInfo prop = injectedType.GetProperty(this.Name);
            if (!prop.CanWrite)
            {
                throw new ConfigurationErrorsException("Cannot inject a readonly property.", this.ElementInformation.Source, this.ElementInformation.LineNumber);
            }
            if (prop.GetIndexParameters().Length > 0)
            {
                throw new ConfigurationErrorsException("Cannot inject a indexed property", this.ElementInformation.Source, this.ElementInformation.LineNumber);
            }

            System.Reflection.MethodInfo setter = prop.GetSetMethod();

            InstanceId depId;
            int order;
            if (string.IsNullOrEmpty(Inject))
            {
                InjectAttribute attr = InjectAttribute.GetFrom(prop);
                if (attr == null)
                {
                    depId = new InstanceId(prop.PropertyType);
                    order = this.Order;
                }
                else
                {
                    depId = new InstanceId(attr.Type == null ? prop.PropertyType : attr.Type, attr.Name);
                    order = attr.Order;
                }
            }
            else
            {
                depId = InstanceId.Parse(Inject);
                order = this.Order;
            }

            IParameter par = new Parameter(setter.GetParameters()[0], depId);
            IInjectionSite site = new InjectionSite(setter, order, new IParameter[] { par });
            return site;
        }
    }
}
